/*
 * Copyright (c) 2010 Vadim Zhukov <persgray@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef OW_PEERTASK_H
#define OW_PEERTASK_H

/*
 * Common structure for all peer messages. Aligned to 64 bits.
 */
struct peer_msg_hdr {
	uint32_t	pmh_session;
	uint32_t	pmh_seq;
#define pmh_error	pmh_seq		/* Used in PEER_CMD_ERROR */
	uint16_t	pmh_code;
	uint16_t	pmh_bufcount;	/* Number of data buffers */
	uint32_t	pmh_flags;
};

enum {
	/*
	 * Collector should resend message to all connected peers (collectors).
	 * (Allowed to) set by filter only.
	 */
	PEER_MSG_F_BROADCAST	= 1
};

enum {
	PEER_ERR_NOERROR	= 0,
	PEER_ERR_PERM		= 1,		/* Operation not permitted */
	PEER_ERR_NOENT		= 2,		/* No such file or directory */
	PEER_ERR_SRCH		= 3,		/* No such process */
	PEER_ERR_INTR		= 4,		/* Interrupted system call */
	PEER_ERR_IO		= 5,		/* Input/output error */
	PEER_ERR_NXIO		= 6,		/* Device not configured */
	PEER_ERR_2BIG		= 7,		/* Argument list too long */
	PEER_ERR_NOEXEC		= 8,		/* Exec format error */
	PEER_ERR_BADF		= 9,		/* Bad file descriptor */
	PEER_ERR_CHILD		= 10,		/* No child processes */
	PEER_ERR_DEADLK		= 11,		/* Resource deadlock avoided */
	PEER_ERR_NOMEM		= 12,		/* Cannot allocate memory */
	PEER_ERR_ACCES		= 13,		/* Permission denied */
	PEER_ERR_FAULT		= 14,		/* Bad address */
	PEER_ERR_NOTBLK		= 15,		/* Block device required */
	PEER_ERR_BUSY		= 16,		/* Device busy */
	PEER_ERR_EXIST		= 17,		/* File exists */
	PEER_ERR_XDEV		= 18,		/* Cross-device link */
	PEER_ERR_NODEV		= 19,		/* Operation not supported by device */
	PEER_ERR_NOTDIR		= 20,		/* Not a directory */
	PEER_ERR_ISDIR		= 21,		/* Is a directory */
	PEER_ERR_INVAL		= 22,		/* Invalid argument */
	PEER_ERR_NFILE		= 23,		/* Too many open files in system */
	PEER_ERR_MFILE		= 24,		/* Too many open files */
	PEER_ERR_NOTTY		= 25,		/* Inappropriate ioctl for device */
	PEER_ERR_TXTBSY		= 26,		/* Text file busy */
	PEER_ERR_FBIG		= 27,		/* File too large */
	PEER_ERR_NOSPC		= 28,		/* No space left on device */
	PEER_ERR_SPIPE		= 29,		/* Illegal seek */
	PEER_ERR_ROFS		= 30,		/* Read-only file system */
	PEER_ERR_MLINK		= 31,		/* Too many links */
	PEER_ERR_PIPE		= 32,		/* Broken pipe */
	PEER_ERR_DOM		= 33,		/* Numerical argument out of domain */
	PEER_ERR_RANGE		= 34,		/* Result too large */
	PEER_ERR_AGAIN		= 35,		/* Resource temporarily unavailable */
	PEER_ERR_WOULDBLOCK	= EAGAIN,	/* Operation would block */
	PEER_ERR_INPROGRESS	= 36,		/* Operation now in progress */
	PEER_ERR_ALREADY	= 37,		/* Operation already in progress */
	PEER_ERR_NOTSOCK	= 38,		/* Socket operation on non-socket */
	PEER_ERR_DESTADDRREQ	= 39,		/* Destination address required */
	PEER_ERR_MSGSIZE	= 40,		/* Message too long */
	PEER_ERR_PROTOTYPE	= 41,		/* Protocol wrong type for socket */
	PEER_ERR_NOPROTOOPT	= 42,		/* Protocol not available */
	PEER_ERR_PROTONOSUPPORT	= 43,	/* Protocol not supported */
	PEER_ERR_SOCKTNOSUPPORT	= 44,	/* Socket type not supported */
	PEER_ERR_OPNOTSUPP	= 45,		/* Operation not supported */
	PEER_ERR_PFNOSUPPORT	= 46,		/* Protocol family not supported */
	PEER_ERR_AFNOSUPPORT	= 47,		/* Address family not supported by protocol family */
	PEER_ERR_ADDRINUSE	= 48,		/* Address already in use */
	PEER_ERR_ADDRNOTAVAIL	= 49,		/* Can't assign requested address */
	PEER_ERR_NETDOWN	= 50,		/* Network is down */
	PEER_ERR_NETUNREACH	= 51,		/* Network is unreachable */
	PEER_ERR_NETRESET	= 52,		/* Network dropped connection on reset */
	PEER_ERR_CONNABORTED	= 53,		/* Software caused connection abort */
	PEER_ERR_CONNRESET	= 54,		/* Connection reset by peer */
	PEER_ERR_NOBUFS		= 55,		/* No buffer space available */
	PEER_ERR_ISCONN		= 56,		/* Socket is already connected */
	PEER_ERR_NOTCONN	= 57,		/* Socket is not connected */
	PEER_ERR_SHUTDOWN	= 58,		/* Can't send after socket shutdown */
	PEER_ERR_TOOMANYREFS	= 59,		/* Too many references: can't splice */
	PEER_ERR_TIMEDOUT	= 60,		/* Operation timed out */
	PEER_ERR_CONNREFUSED	= 61,		/* Connection refused */
	PEER_ERR_LOOP		= 62,		/* Too many levels of symbolic links */
	PEER_ERR_NAMETOOLONG	= 63,		/* File name too long */
	PEER_ERR_HOSTDOWN	= 64,		/* Host is down */
	PEER_ERR_HOSTUNREACH	= 65,		/* No route to host */
	PEER_ERR_NOTEMPTY	= 66,		/* Directory not empty */
	PEER_ERR_PROCLIM	= 67,		/* Too many processes */
	PEER_ERR_USERS		= 68,		/* Too many users */
	PEER_ERR_DQUOT		= 69,		/* Disk quota exceeded */
	PEER_ERR_STALE		= 70,		/* Stale NFS file handle */
	PEER_ERR_REMOTE		= 71,		/* Too many levels of remote in path */
	PEER_ERR_BADRPC		= 72,		/* RPC struct is bad */
	PEER_ERR_RPCMISMATCH	= 73,		/* RPC version wrong */
	PEER_ERR_PROGUNAVAIL	= 74,		/* RPC prog. not avail */
	PEER_ERR_PROGMISMATCH	= 75,		/* Program version wrong */
	PEER_ERR_PROCUNAVAIL	= 76,		/* Bad procedure for program */
	PEER_ERR_NOLCK		= 77,		/* No locks available */
	PEER_ERR_NOSYS		= 78,		/* Function not implemented */
	PEER_ERR_FTYPE		= 79,		/* Inappropriate file type or format */
	PEER_ERR_AUTH		= 80,		/* Authentication error */
	PEER_ERR_NEEDAUTH	= 81,		/* Need authenticator */
	PEER_ERR_IPSEC		= 82,		/* IPsec processing failure */
	PEER_ERR_NOATTR		= 83,		/* Attribute not found */
	PEER_ERR_ILSEQ		= 84,		/* Illegal byte sequence */
	PEER_ERR_NOMEDIUM	= 85,		/* No medium found */
	PEER_ERR_MEDIUMTYPE	= 86,		/* Wrong Medium Type */
	PEER_ERR_OVERFLOW	= 87,		/* Conversion overflow */
	PEER_ERR_CANCELED	= 88,		/* Operation canceled */
	PEER_ERR_IDRM		= 89,		/* Identifier removed */
	PEER_ERR_NOMSG		= 90,		/* No message of desired type */
	PEER_ERR_NOTSUP		= 91,		/* Not supported */
	PEER_ERR_LAST		= 91,		/* Must be equal largest errno */
};

struct peer_msg_hello {
	uint32_t	version;
	char		peer_name[];   /* detemine actual length from datalen */
};

enum { PEER_VERSION = 1 };

struct peer_msg_last_session_resp {
	uint32_t	lastsession;
	uint32_t	unused;
	char		name[];
};

struct peer_msg_conf_changed {
	uint32_t	op;
	uint32_t	objflags;	/* Object-dependent flags, if needed */
	char		objname[];
	/* Additional parameters go in separate buffers in form NAME=VALUE */
};

enum {
	PEER_CONF_OP_ADDOBJ,
	PEER_CONF_OP_DELOBJ,
};

struct peer_msg_filter_logs_op {
	/* Two timestamps */
	uint64_t	from;
	uint64_t	till;

	uint16_t	unused;

	/*
	 * The name of host to operate on the logs of. If omitted then
	 * operation is taken on logs of all hosts. The actual data starts
	 * immediately after host_name.
	 */
	uint16_t	host_name_len;
	char		host_name[];
};

enum {
	/*
	 * Indicates we did not set up or received header yet. If it appears
	 * on the wire then connection should be dropped.
	 */
	PEER_CMD_UNKNOWN,

	/*
	 * May be sent between any peers at any time.
	 * May be used as "ping" command.
	 */
	PEER_CMD_NOOP,

	/*
	 * Indicates that corresponding (see "seq") request made by previously
	 * by receiver of this message was completed.
	 */
	PEER_CMD_OK,

	/*
	 * Peer should send this message when it determines something gone
	 * wrong. For example, if OK message timeout exceeded. The action to be
	 * taken depends on the error. Error code is written in the place of
	 * "seq" as this message sent "asynchronously".
	 */
	PEER_CMD_ERROR,

	/*
	 * Transmitting a line from sensor to database.
	 */
	PEER_CMD_LINE,

	/*
	 * First message that should be send from connecting peer after
	 * successful connection establishment. Peer sent this message may
	 * receive some CONF_CHANGED commands, and then it'll always
	 * receive HELLO_RESPONSE message. Upon receiving OK message the
	 * connection is fully established.
	 */
	PEER_CMD_HELLO,

	/*
	 * Finalizes session establishment. If corresponding HELLO message had
	 * session code set to zero, new session code is returned.
	 */
	PEER_CMD_HELLO_RESPONSE,

	/*
	 * Sent by controller to filter. Includes new configuration parameters.
	 * They should be written to database, OK message sent back and then
	 * filter should send CONF_CHANGED command to:
	 *  1) All monitors connected;
	 *  2) Other collectors if message was not received from collector.
	 */
	PEER_CMD_CONF_UPDATE,

	/*
	 * Contain new configuration parameters for the peer received this
	 * message. See CONF_UPDATE.
	 */
	PEER_CMD_CONF_CHANGED,

	/*
	 * Send by collector to local filter and other collectors when a monitor
	 * approaches with HELLO message which has session set to zero.
	 * Should be answered with LAST_SESSION_RESPONSE.
	 */
	PEER_CMD_LAST_SESSION_QUERY,

	/*
	 * Contains maximum session ID that sending peer seen for monitor, as
	 * asked above.
	 */
	PEER_CMD_LAST_SESSION_RESPONSE,

	/*
	 * Sent by controller and should be delivered to local filter only.
	 * Also sent by filter upon start, used for database synchronization.
	 */
	PEER_CMD_GET_LOGS,

	/*
	 * Initially, when got by collector from controller, this message should
	 * be delivered to local filter only. Then filter adds a filter task in
	 * corresponding database table and sends this message to other filters
	 * (using his collector, of course).
	 */
	PEER_CMD_DROP_LOGS,

	/*
	 * Count of command codes.
	 *
	 * N.B.: Sync peer_cmd_info[] in peertask.c on updates.
	 */
	PEER_CMD_COUNT
};

struct peer_data_buffer {
	uint32_t	refs;

	/*
	 * Host byte order (sending/receiving in network byte order, of course).
	 * Value includes data only. Should preceed "data" immediately as we are
	 * cheating - write them at once.
	 */
	uint32_t	len;

	/*
	 * The actual buffer is always null-terminated, but '\0' does not count
	 * in "len" above. This makes handling of strings being sent in buffers
	 * way more easy.
	 *
	 * NB: Care of 64-bit alignment.
	 */
	char		data[];
};

enum { MAX_TASK_BUFFERS = 32 };

/*
 * Host peer/tasks management. Use common types, less strict align rules.
 */

SIMPLEQ_HEAD(peer_task_queue, peer_task);
struct peer_task {
	SIMPLEQ_ENTRY(peer_task)	 entry;

	/* Task that provoked creation of this one. */
	struct peer_task		*provoker;

	/*
	 * List of tasks that were provoked by this one. Could contain gaps
	 * (NULL values) after freeing some of the followers. We do not bother
	 * about removing items since task objects are not permanent anyway.
	 */
	struct peer_task		*followers;
	size_t				 nfollowers;

	struct peer_msg_hdr		 cmd;

	/*
	 * Each task may consists of up to MAX_TASK_BUFFERS buffers. Feel free
	 * to crank this value up as needed. Unused buffer pointers should
	 * always be NULL.
	 *
	 * Usage of buffers is command-specific.
	 */
	struct peer_data_buffer		*databuf[MAX_TASK_BUFFERS];

	time_t				 started;
	ssize_t				 phase_progress;
	uint32_t			 _buf_len;	/* internal usage */
	int				 phase;
	int				 error;
};

enum { PEER_TASK_TIMEOUT = 10 };	/* seconds */

enum {
	PEER_TASK_PHASE_START,		/* in queue */
	PEER_TASK_PHASE_CMD,		/* processing header */
	PEER_TASK_PHASE_DATA,		/* processing data buffers */
	PEER_TASK_PHASE_WAIT,		/* waiting for OK or other response */
	PEER_TASK_PHASE_DONE		/* success or failure */
};

#define peer_task_failed(task)	((task)->phase == PEER_TASK_PHASE_DONE && \
				    (task)->cmd.pmh_error != 0)
#define task_cmd_code(task)	(ntohs((task)->cmd.pmh_code))
#define task_buf_count(task)	(ntohs((task)->cmd.pmh_bufcount))
#define is_ok_task(task)	(task_cmd_code(task) == PEER_CMD_OK)
#define is_error_task(task)	(task_cmd_code(task) == PEER_CMD_ERROR)
#define is_line_task(task)	(task_cmd_code(task) == PEER_CMD_LINE)
#define is_hello_task(task)	(task_cmd_code(task) == PEER_CMD_HELLO)
#define is_conf_task(task)	(task_cmd_code(task) == PEER_CMD_CONF_CHANGED)

#define DPTASK(n, task, text...)	do { \
	if (debug >= n) { \
		fprintf(stderr, text); \
		peer_task_dump(stderr, (task), 0); \
	} \
} while(0)


struct peer_task	*peer_new_task(size_t, struct peer_task *);
struct peer_data_buffer	*peer_new_data_buffer(size_t, const void *);
void			 peer_free_unused_data_buffer(struct peer_data_buffer*);
void			 peer_free_data_buffer(struct peer_data_buffer *);
void			 peer_free_task_data_buffer(struct peer_task *, size_t);
void			 peer_init_task(struct peer_task *, int);
void			 peer_init_task_from(struct peer_task *,
			    const struct peer_task *, size_t);
void			 peer_add_task_data(struct peer_task *,
			    struct peer_data_buffer *, ssize_t);
void			 peer_init_error_task(struct peer_task *, int);
void			 peer_convert_task_to_ok(struct peer_task *);
void			 peer_convert_task_to_last_sess_response(
			    struct peer_task *);
void			 peer_convert_task_to_hello_response(struct peer_task *,
			    uint32_t);
void			 peer_convert_task_to_error(struct peer_task *, int);
void			 peer_free_task_resources(struct peer_task *);
int			 peer_free_task(struct peer_task *, size_t);
void			 peer_task_dump(FILE *, struct peer_task *, int);

#endif /* OW_PEERTASK_H */
